/* @flow */

import he from 'he'
import { parseHTML } from './html-parser'
import { parseText } from './text-parser'
import { parseFilters } from './filter-parser'
import { genAssignmentCode } from '../directives/model'
import { extend, cached, no, camelize, hyphenate } from 'shared/util'
import { isIE, isEdge, isServerRendering } from 'core/util/env'

import {
  addProp,
  addAttr,
  baseWarn,
  addHandler,
  addDirective,
  getBindingAttr,
  getAndRemoveAttr,
  getRawBindingAttr,
  pluckModuleFunction,
  getAndRemoveAttrByRegex
} from '../helpers'

//匹配@或v-on开头的字符串，主要检测是否是监听事件的指令
//在 `vue` 中所有以 `v-` 开头的属性都被认为是指令，
//另外 `@` 字符是 `v-on` 的缩写，`:` 字符是 `v-bind` 的缩写
export const onRE = /^@|^v-on:/

//匹配以字符 `v-` 或 `@` 或 `:` 开头的字符串
export const dirRE = process.env.VBIND_PROP_SHORTHAND
  ? /^v-|^@|^:|^\.|^#/
  : /^v-|^@|^:|^#/

//匹配v-for指令的值,如v-for="obj of list"那么捕获到的就是obj和list
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/

//因为v-for循环的解构有多种写法，这是捕获v-for="(item,index) in list"
//或者是v-for="(value,key, index) in list"这两种写法的
export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/

//配合forIteratorRE指令使用的，目的是为了去掉括号，如(value,key,index)
const stripParensRE = /^\(|\)$/g

const dynamicArgRE = /^\[.*\]$/

//匹配指令中的参数,如v-on:click.stop="handleClick"得到click.stop
const argRE = /:(.*)$/

//匹配字符 `:` 或字符串 `v-bind:` 开头的字符串
export const bindRE = /^:|^\.|^v-bind:/

//匹配一个逗号
const propBindRE = /^\./

//匹配修饰符,如@click.stop或者@click['stop']
const modifierRE = /\.[^.\]]+(?=[^\]]*$)/g

const slotRE = /^v-slot(:|$)|^#/

const lineBreakRE = /[\r\n]/
const whitespaceRE = /\s+/g

const invalidAttributeRE = /[\s"'<>\/=]/

//HTML 实体解码函数 decodeHTMLCached
//如 `&#x26;` 代表的字符为 `&`
const decodeHTMLCached = cached(he.decode)

export const emptySlotScopeToken = `_empty_`

// configurable state
export let warn: any
let delimiters
let transforms
let preTransforms
let postTransforms
let platformIsPreTag
let platformMustUseProp
let platformGetTagNamespace
let maybeComponent


/**
 * 将输入的属性转为AST抽象语法树的形式
 * @param {} tag 标签明
 * @param {*} attrs 属性
 * @param {*} parent 父级
 * 输入： tag: div,  attrs: {end: 13, name: "id", start: 5, value: "app"} , parent: undefined
 * 输出： 
 *    {
 *      attrsList: [{ "name": "id","value": "app" }],
        attrsMap: {  "id": "app" },
        children: [],
        parent: undefined,
        rawAttrsMap: {},
        tag: "div",
        type: 1
      }
 */
export function createASTElement(
  tag: string,
  attrs: Array<ASTAttr>,
  parent: ASTElement | void
): ASTElement {
  return {
    type: 1,
    tag,
    attrsList: attrs,
    attrsMap: makeAttrsMap(attrs),
    rawAttrsMap: {},
    parent,
    children: []
  }
}

/**
 * Convert HTML string to AST.
 */
export function parse(
  template: string,
  options: CompilerOptions
): ASTElement | void {
  //这里根据平台来配置一些参数，因为要做兼容
  //打印信息
  warn = options.warn || baseWarn

  //是不是pre标签
  platformIsPreTag = options.isPreTag || no

  platformMustUseProp = options.mustUseProp || no
  platformGetTagNamespace = options.getTagNamespace || no
  const isReservedTag = options.isReservedTag || no
  maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)


  /**
   * options.modules是一个对象，在platforms/compiler/modules/index.js导出的
   * pluckModuleFunction就是你传递一个key给它，然后它从options.modules取出key对应的值，
   * 如果值是数组直接返回，否则就将值装到数组再返回
   */
  transforms = pluckModuleFunction(options.modules, 'transformNode')
  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
  //在new Vue传递的一个数组，就是改变{{}}写法的数组
  delimiters = options.delimiters

  const stack = []
  const preserveWhitespace = options.preserveWhitespace !== false
  const whitespaceOption = options.whitespace
  let root  //根标签Ast
  let currentParent //当前正在解析的，且内容是标签，不是字符串文本的引用，提供给下一次解析时需要引用父类标签时用到
  let inVPre = false
  let inPre = false
  let warned = false

  function warnOnce(msg, range) {
    if (!warned) {
      warned = true
      warn(msg, range)
    }
  }

  function closeElement(element) {
    //因为匹配到了闭合标签，因此当前算是一个回路结束了、
    //trimEndingWhitespace把当前标签的子类查看一下有没有一些空格子元素，然后去掉
    trimEndingWhitespace(element)
    if (!inVPre && !element.processed) {
      //processElement方法基本是解析到了标签上所有的属性，然后完善AST对象的属性
      element = processElement(element, options)
    }
    // tree management
    if (!stack.length && element !== root) {
      // allow root elements with v-if, v-else-if and v-else
      if (root.if && (element.elseif || element.else)) {
        if (process.env.NODE_ENV !== 'production') {
          checkRootConstraints(element)
        }
        addIfCondition(root, {
          exp: element.elseif,
          block: element
        })
      } else if (process.env.NODE_ENV !== 'production') {
        warnOnce(
          `Component template should contain exactly one root element. ` +
          `If you are using v-if on multiple elements, ` +
          `use v-else-if to chain them instead.`,
          { start: element.start }
        )
      }
    }
    if (currentParent && !element.forbidden) {
      if (element.elseif || element.else) {
        processIfConditions(element, currentParent)
      } else {
        if (element.slotScope) {
          // scoped slot
          // keep it in the children list so that v-else(-if) conditions can
          // find it as the prev node.
          const name = element.slotTarget || '"default"'
            ; (currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
        }
        //解析完属性，存放到上个父级，解析规则是这样的，先解析到所有子类，然后通过子类加到父类上，逆推上去的
        currentParent.children.push(element)
        element.parent = currentParent
      }
    }


    // 得到没有slotScope属性的子类
    element.children = element.children.filter(c => !(c: any).slotScope)
    // 继续提出空字符串子类
    trimEndingWhitespace(element)

    // check pre state
    if (element.pre) {
      inVPre = false
    }

    //判断当前标签是不是Pre标签
    if (platformIsPreTag(element.tag)) {
      inPre = false
    }
    // apply post-transforms 暂时未知道干嘛的
    for (let i = 0; i < postTransforms.length; i++) {
      postTransforms[i](element, options)
    }
  }

  function trimEndingWhitespace(el) {
    // remove trailing whitespace node
    if (!inPre) {
      let lastNode
      while (
        (lastNode = el.children[el.children.length - 1]) &&
        lastNode.type === 3 &&
        lastNode.text === ' '
      ) {
        el.children.pop()
      }
    }
  }

  function checkRootConstraints(el) {
    if (el.tag === 'slot' || el.tag === 'template') {
      warnOnce(
        `Cannot use <${el.tag}> as component root element because it may ` +
        'contain multiple nodes.',
        { start: el.start }
      )
    }
    if (el.attrsMap.hasOwnProperty('v-for')) {
      warnOnce(
        'Cannot use v-for on stateful component root element because ' +
        'it renders multiple elements.',
        el.rawAttrsMap['v-for']
      )
    }
  }

  parseHTML(template, {
    warn,
    expectHTML: options.expectHTML,
    isUnaryTag: options.isUnaryTag,
    canBeLeftOpenTag: options.canBeLeftOpenTag,
    shouldDecodeNewlines: options.shouldDecodeNewlines,
    shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
    shouldKeepComment: options.comments,
    outputSourceRange: options.outputSourceRange,

  /** 1、`start` 钩子函数是当解析 `html` 字符串遇到开始标签时被调用的。
    * 2、模板中禁止使用 `<style>` 标签和那些没有指定 `type` 属性或 `type` 属性值为 `text/javascript` 的 `<script>` 标签。
    * 3、在 `start` 钩子函数中会调用前置处理函数，这些前置处理函数都放在 `preTransforms` 数组中，这么做的目的是为不同平台提供对应平台下的解析工作。
    * 4、前置处理函数执行完之后会调用一系列 `process*` 函数继续对元素描述对象进行加工。
    * 5、通过判断 `root` 是否存在来判断当前解析的元素是否为根元素。
    * 6、`slot` 标签和 `template` 标签不能作为根元素，并且根元素不能使用 `v-for` 指令。
    * 7、可以定义多个根元素，但必须使用 `v-if`、`v-else-if` 以及 `v-else` 保证有且仅有一个根元素被渲染。
    * 8、构建 `AST` 并建立父子级关系是在 `start` 钩子函数中完成的，每当遇到非一元标签，会把它存到 `currentParent` 变量中，当解析该标签的子节点时通过访问 `currentParent` 变量获取父级元素。
    * 9、如果一个元素使用了 `v-else-if` 或 `v-else` 指令，则该元素不会作为子节点，而是会被添加到相符的使用了 `v-if` 指令的元素描述对象的 `ifConditions` 数组中。
    * 10、如果一个元素使用了 `slot-scope` 特性，则该元素也不会作为子节点，它会被添加到父级元素描述对象的 `scopedSlots` 属性中。
    * 11、对于没有使用条件指令或 `slot-scope` 特性的元素，会正常建立父子级关系。
    */
    start(tag, attrs, unary, start, end) {
      //检查父级是否存在命名空间,如
      //<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
      //<rect x="20" y="20" width="250" height="250" style="fill:blue;"/>
      //</svg>
      const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)

      // handle IE svg bug
      /* istanbul ignore if */
      if (isIE && ns === 'svg') {
        attrs = guardIESVGBug(attrs)
      }

      //生成抽象AST语法树对象
      let element: ASTElement = createASTElement(tag, attrs, currentParent)
      if (ns) {
        element.ns = ns
      }

      if (process.env.NODE_ENV !== 'production') {
        if (options.outputSourceRange) {
          element.start = start
          element.end = end
          element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => {
            cumulated[attr.name] = attr
            return cumulated
          }, {})
        }
        attrs.forEach(attr => {
          if (invalidAttributeRE.test(attr.name)) {
            warn(
              `Invalid dynamic argument expression: attribute names cannot contain ` +
              `spaces, quotes, <, >, / or =.`,
              {
                start: attr.start + attr.name.indexOf(`[`),
                end: attr.start + attr.name.length
              }
            )
          }
        })
      }

      /**
       * isForbiddenTag判断是否是style或者是script标签
       */
      if (isForbiddenTag(element) && !isServerRendering()) {
        element.forbidden = true
        process.env.NODE_ENV !== 'production' && warn(
          'Templates should only be responsible for mapping the state to the ' +
          'UI. Avoid placing tags with side-effects in your templates, such as ' +
          `<${tag}>` + ', as they will not be parsed.',
          { start: element.start }
        )
      }

      // apply pre-transforms
      for (let i = 0; i < preTransforms.length; i++) {
        /**
         * ppreTransforms是对input标签进行了处理
         */
        element = preTransforms[i](element, options) || element
      }

      if (!inVPre) {
        //判断元素上是否存在v-pre指令
        processPre(element)
        if (element.pre) {
          inVPre = true
        }
      }

      //platformIsPreTag方法就是单纯判断标签是否是pre标签
      if (platformIsPreTag(element.tag)) {
        inPre = true
      }
      if (inVPre) {
        //处理v-pre指令
        processRawAttrs(element)
      } else if (!element.processed) {

        //解析v-for指令
        processFor(element)
        //解析v-if指令
        processIf(element)
        //解析v-once指令
        processOnce(element)
      }

      if (!root) {
        root = element
        if (process.env.NODE_ENV !== 'production') {
          //这里不必例会，就检查一下标签是不是slot和template，还有就是一些特殊名字
          checkRootConstraints(root)
        }
      }

      //unary是从html-parser.js传递过来的，判断是不是特定的一些标签，也就是一员元素，一元元素没有闭合标签
      if (!unary) {
        currentParent = element
        stack.push(element)
      } else {
        //是一元元素，直接进行闭合操作
        closeElement(element)
      }
    },

    //结束标签
    end(tag, start, end) {

      const element = stack[stack.length - 1]
      // pop stack
      stack.length -= 1
      //得到当前标签的符合
      currentParent = stack[stack.length - 1]
      if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
        element.end = end
      }
      closeElement(element)
    },
    /**
     * 解析字符串文本内容
     * @param {d} text  需要解析的字符串
     * @param {*} start  当前字符串在剩余解析内容template的开始索引
     * @param {*} end   当前字符串在剩余解析内容template的结束索引
     */
    chars(text: string, start: number, end: number) {
      //动态父级标签不存在，也就是只有<template></template>这样的情况时就会弹出的提示
      if (!currentParent) {
        if (process.env.NODE_ENV !== 'production') {
          if (text === template) {
            warnOnce(
              'Component template requires a root element, rather than just text.',
              { start }
            )
          } else if ((text = text.trim())) {
            warnOnce(
              `text "${text}" outside root element will be ignored.`,
              { start }
            )
          }
        }
        return
      }
      // IE textarea placeholder bug
      /* istanbul ignore if */
      if (isIE &&
        currentParent.tag === 'textarea' &&
        currentParent.attrsMap.placeholder === text
      ) {
        return
      }

      // currentParent.children 是在createASTElement方法里面创建的 
      const children = currentParent.children
      //inPre是个全局属性，是上一次循环留下来的产物，目的是为了判断是不是Pre标签，如果是，那么当前本次匹配的内容就是pre里面的字符串了
      if (inPre || text.trim()) {
        //isTextTag方法判断当前文本内容的父级是不是script || style标签
        text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
      } else if (!children.length) {
        text = ''
      } else if (whitespaceOption) {
        if (whitespaceOption === 'condense') {
          // in condense mode, remove the whitespace node if it contains
          // line break, otherwise condense to a single space
          text = lineBreakRE.test(text) ? '' : ' '
        } else {
          text = ' '
        }
      } else {
        text = preserveWhitespace ? ' ' : ''
      }
      if (text) {
        if (!inPre && whitespaceOption === 'condense') {
          // condense consecutive whitespaces into single space
          text = text.replace(whitespaceRE, ' ')
        }
        let res
        let child: ?ASTNode
        /**
         * parseText解析{{}}里面的内容，parseText是text-parser.js解析器的方法，其还调用了filter-parser.js来解析过滤其的内容
         * 输入： {{ message }}
         * 输出： { expression:  _s(message), tokens: [ { @binding: "message" } ] }
         */
        if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
          child = {
            type: 2,
            expression: res.expression,
            tokens: res.tokens,
            text
          }
        } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
          child = {
            type: 3,
            text
          }
        }
        if (child) {
          if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
            child.start = start
            child.end = end
          }
          //child: { 
          //  type: 2, 
          //  expression: _s(message),
          //  tokens: [ { @binding: "message" } ],
          //  text: '{{ message }}'
          //}
          //加入到上一个parent的子集里面
          children.push(child)
        }
      }
    },
    comment(text: string, start, end) {
      // adding anyting as a sibling to the root node is forbidden
      // comments should still be allowed, but ignored
      if (currentParent) {
        const child: ASTText = {
          type: 3,
          text,
          isComment: true
        }
        if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
          child.start = start
          child.end = end
        }
        currentParent.children.push(child)
      }
    }
  })
  return root
}

//如果存在v-pre标志，那么为AST添加pre属性
function processPre(el) {
  if (getAndRemoveAttr(el, 'v-pre') != null) {
    el.pre = true
  }
}

/**
 * 如果当前标签存在v-pre指令那么就给
 * 给AST对象添加attrs, 其与attrsList的结构相同
 * 如果当前标签是v-pre指令的字节，那么给其AST添加plain=true
 * @param {*} el 
 */
function processRawAttrs(el) {
  const list = el.attrsList
  const len = list.length
  if (len) {
    const attrs: Array<ASTAttr> = el.attrs = new Array(len)
    for (let i = 0; i < len; i++) {
      attrs[i] = {
        name: list[i].name,
        value: JSON.stringify(list[i].value)
      }
      if (list[i].start != null) {
        attrs[i].start = list[i].start
        attrs[i].end = list[i].end
      }
    }
  } else if (!el.pre) {
    //这个else if 分子就是v-pre的子类
    // <div v-pre> 
    //   <span></span>
    // </div>
    // non root node in pre blocks with no attributes
    el.plain = true
  }
}

export function processElement(
  element: ASTElement,
  options: CompilerOptions
) {
  //检查一下动态属性key，如果存在就挂在到AST对象element
  processKey(element)

  // determine whether this is a plain element after
  // removing structural attributes
  element.plain = (
    !element.key &&
    !element.scopedSlots &&
    !element.attrsList.length
  )

  //检查一下动态属性ref，如果存在就挂在到AST对象element
  processRef(element)
  //检查一下scope和slot-scope，如果存在就挂在到AST对象element
  processSlotContent(element)
  //检查一下是不是<slot/>标签，如果存在就挂在到AST对象element
  processSlotOutlet(element)
  //检查一下是不是<component/>标签，如果存在就挂在到AST对象element
  processComponent(element)
  //transforms里面有两个方法，分别是检查class和style, 两者的属性可以是动态的
  for (let i = 0; i < transforms.length; i++) {
    element = transforms[i](element, options) || element
  }

  /**
   * 解析一下其他的属性就是arrlist里面的杂项
   * 上面我们已经解析的属性如下
   *  `v-pre`
    * `v-for`
    * `v-if`、`v-else-if`、`v-else`
    * `v-once`
    * `key`
    * `ref`
    * `slot`、`slot-scope`、`scope`、`name`
    * `is`、`inline-template`
   */
  processAttrs(element)
  return element
}

function processKey(el) {
  //检查是否存在动态属性key，然后返回其值，挂在到AST对象上
  const exp = getBindingAttr(el, 'key')
  if (exp) {
    if (process.env.NODE_ENV !== 'production') {
      if (el.tag === 'template') {
        warn(
          `<template> cannot be keyed. Place the key on real elements instead.`,
          getRawBindingAttr(el, 'key')
        )
      }
      if (el.for) {
        const iterator = el.iterator2 || el.iterator1
        const parent = el.parent
        if (iterator && iterator === exp && parent && parent.tag === 'transition-group') {
          warn(
            `Do not use v-for index as key on <transition-group> children, ` +
            `this is the same as not using keys.`,
            getRawBindingAttr(el, 'key'),
            true /* tip */
          )
        }
      }
    }
    el.key = exp
  }
}

function processRef(el) {
  const ref = getBindingAttr(el, 'ref')
  if (ref) {
    el.ref = ref
    el.refInFor = checkInFor(el)
  }
}

export function processFor(el: ASTElement) {
  let exp
  if ((exp = getAndRemoveAttr(el, 'v-for'))) {
    const res = parseFor(exp)
    if (res) {
      extend(el, res)
    } else if (process.env.NODE_ENV !== 'production') {
      warn(
        `Invalid v-for expression: ${exp}`,
        el.rawAttrsMap['v-for']
      )
    }
  }
}

type ForParseResult = {
  for: string;
  alias: string;
  iterator1?: string;
  iterator2?: string;
};

/**
 * 拆解一下for指令的值
 * 如输入：exp = item in arr
 * 输出为: {  alias: 'item', for: 'arr' }
 * @param {} exp 
 */
export function parseFor(exp: string): ?ForParseResult {
  const inMatch = exp.match(forAliasRE)
  if (!inMatch) return
  const res = {}
  res.for = inMatch[2].trim()
  const alias = inMatch[1].trim().replace(stripParensRE, '')
  const iteratorMatch = alias.match(forIteratorRE)
  if (iteratorMatch) {
    res.alias = alias.replace(forIteratorRE, '').trim()
    res.iterator1 = iteratorMatch[1].trim()
    if (iteratorMatch[2]) {
      res.iterator2 = iteratorMatch[2].trim()
    }
  } else {
    res.alias = alias
  }
  return res
}


/**
 * 解析if指令
 * 输入: AST
 * 输出后：在el上挂载属性 el: { ..., if: 值 , else: 值,  elseif: 值}
 */
function processIf(el) {
  const exp = getAndRemoveAttr(el, 'v-if')
  if (exp) {
    el.if = exp
    addIfCondition(el, {
      exp: exp,
      block: el
    })
  } else {
    if (getAndRemoveAttr(el, 'v-else') != null) {
      el.else = true
    }
    const elseif = getAndRemoveAttr(el, 'v-else-if')
    if (elseif) {
      el.elseif = elseif
    }
  }
}

function processIfConditions(el, parent) {
  const prev = findPrevElement(parent.children)
  if (prev && prev.if) {
    addIfCondition(prev, {
      exp: el.elseif,
      block: el
    })
  } else if (process.env.NODE_ENV !== 'production') {
    warn(
      `v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` +
      `used on element <${el.tag}> without corresponding v-if.`,
      el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else']
    )
  }
}

function findPrevElement(children: Array<any>): ASTElement | void {
  let i = children.length
  while (i--) {
    if (children[i].type === 1) {
      return children[i]
    } else {
      if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') {
        warn(
          `text "${children[i].text.trim()}" between v-if and v-else(-if) ` +
          `will be ignored.`,
          children[i]
        )
      }
      children.pop()
    }
  }
}

export function addIfCondition(el: ASTElement, condition: ASTIfCondition) {
  if (!el.ifConditions) {
    el.ifConditions = []
  }
  el.ifConditions.push(condition)
}

//如果解析到v-once指令，那么就在el上添加属性once = true
function processOnce(el) {
  const once = getAndRemoveAttr(el, 'v-once')
  if (once != null) {
    el.once = true
  }
}

//获取当前AST标签上的插槽的值
//插槽的值的写法有两种: 1.scope  2.slot-scope 
//然后挂在到AST上面
function processSlotContent(el) {
  let slotScope
  if (el.tag === 'template') {
    slotScope = getAndRemoveAttr(el, 'scope')
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && slotScope) {
      warn(
        `the "scope" attribute for scoped slots have been deprecated and ` +
        `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
        `can also be used on plain elements in addition to <template> to ` +
        `denote scoped slots.`,
        el.rawAttrsMap['scope'],
        true
      )
    }
    el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
  } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
      warn(
        `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
        `(v-for takes higher priority). Use a wrapper <template> for the ` +
        `scoped slot to make it clearer.`,
        el.rawAttrsMap['slot-scope'],
        true
      )
    }
    el.slotScope = slotScope
  }

  // slot="xxx"
  const slotTarget = getBindingAttr(el, 'slot')
  if (slotTarget) {
    el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
    el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot'])
    // preserve slot as an attribute for native shadow DOM compat
    // only for non-scoped slots.
    if (el.tag !== 'template' && !el.slotScope) {
      addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
    }
  }

  // 2.6 v-slot syntax
  if (process.env.NEW_SLOT_SYNTAX) {
    if (el.tag === 'template') {
      // v-slot on <template>
      const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
      if (slotBinding) {
        if (process.env.NODE_ENV !== 'production') {
          if (el.slotTarget || el.slotScope) {
            warn(
              `Unexpected mixed usage of different slot syntaxes.`,
              el
            )
          }
          if (el.parent && !maybeComponent(el.parent)) {
            warn(
              `<template v-slot> can only appear at the root level inside ` +
              `the receiving component`,
              el
            )
          }
        }
        const { name, dynamic } = getSlotName(slotBinding)
        el.slotTarget = name
        el.slotTargetDynamic = dynamic
        el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf
      }
    } else {
      // v-slot on component, denotes default slot
      const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
      if (slotBinding) {
        if (process.env.NODE_ENV !== 'production') {
          if (!maybeComponent(el)) {
            warn(
              `v-slot can only be used on components or <template>.`,
              slotBinding
            )
          }
          if (el.slotScope || el.slotTarget) {
            warn(
              `Unexpected mixed usage of different slot syntaxes.`,
              el
            )
          }
          if (el.scopedSlots) {
            warn(
              `To avoid scope ambiguity, the default slot should also use ` +
              `<template> syntax when there are other named slots.`,
              slotBinding
            )
          }
        }
        // add the component's children to its default slot
        const slots = el.scopedSlots || (el.scopedSlots = {})
        const { name, dynamic } = getSlotName(slotBinding)
        const slotContainer = slots[name] = createASTElement('template', [], el)
        slotContainer.slotTarget = name
        slotContainer.slotTargetDynamic = dynamic
        slotContainer.children = el.children.filter((c: any) => {
          if (!c.slotScope) {
            c.parent = slotContainer
            return true
          }
        })
        slotContainer.slotScope = slotBinding.value || emptySlotScopeToken
        // remove children as they are returned from scopedSlots now
        el.children = []
        // mark el non-plain so data gets generated
        el.plain = false
      }
    }
  }
}

function getSlotName(binding) {
  let name = binding.name.replace(slotRE, '')
  if (!name) {
    if (binding.name[0] !== '#') {
      name = 'default'
    } else if (process.env.NODE_ENV !== 'production') {
      warn(
        `v-slot shorthand syntax requires a slot name.`,
        binding
      )
    }
  }
  return dynamicArgRE.test(name)
    // dynamic [name]
    ? { name: name.slice(1, -1), dynamic: true }
    // static name
    : { name: `"${name}"`, dynamic: false }
}

//如果当前传入的AST是<slot/>标签，那么获取其name
//默认插槽
//  <slot></slot>
//别名插槽
//  <slot name="header"></slot>
//插槽内容
//  <h1 slot="header">title</h1>
//作用域插槽 - slot-scope
//  <h1 slot="header" slot-scope="slotProps">{{slotProps}}</h1>
//作用域插槽 - scope:
//  <template slot="header" scope="slotProps">
//    <h1>{{slotProps}}</h1>
//  </template>
function processSlotOutlet(el) {
  if (el.tag === 'slot') {
    el.slotName = getBindingAttr(el, 'name')
    if (process.env.NODE_ENV !== 'production' && el.key) {
      warn(
        `\`key\` does not work on <slot> because slots are abstract outlets ` +
        `and can possibly expand into multiple elements. ` +
        `Use the key on a wrapping element instead.`,
        getRawBindingAttr(el, 'key')
      )
    }
  }
}

//检查AST标签是不是<component/>
function processComponent(el) {
  let binding
  if ((binding = getBindingAttr(el, 'is'))) {
    el.component = binding
  }
  if (getAndRemoveAttr(el, 'inline-template') != null) {
    el.inlineTemplate = true
  }
}

/**
 * 接下剩下的一些其他属性
 * 如输入attrsList:  = [
 *   {name: "@click", value: "clickEvent", start: 28, end: 47}
 *   {name: ":name", value: "message", start: 48, end: 63}
 *   {name: "name", value: "1111", start: 64, end: 75}
 *   {name: "v-bind:hello", value: "message", start: 89, end: 111}
 *]
 * @param {*} el 
 */
function processAttrs(el) {
  const list = el.attrsList
  let i, l, name, rawName, value, modifiers, syncGen, isDynamic
  for (i = 0, l = list.length; i < l; i++) {
    name = rawName = list[i].name
    value = list[i].value
    //是否以 `v-`、`@` 或 `:` 开头的绑定属性
    if (dirRE.test(name)) {
      // mark element as dynamic
      el.hasBindings = true
      // 匹配修饰符 匹配成功就返回{ stop: true, prevent: true } 的形式
      modifiers = parseModifiers(name.replace(dirRE, ''))
      // support .foo shorthand syntax for the .prop modifier
      if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
        (modifiers || (modifiers = {})).prop = true
        name = `.` + name.slice(1).replace(modifierRE, '')
      } else if (modifiers) {
        name = name.replace(modifierRE, '')
      }
      if (bindRE.test(name)) { // v-bind或者:
        //去掉:或者v-bind符号
        name = name.replace(bindRE, '')
        //解析一下值是否存在过滤器
        value = parseFilters(value)
        isDynamic = dynamicArgRE.test(name)
        if (isDynamic) {
          name = name.slice(1, -1)
        }
        if (
          process.env.NODE_ENV !== 'production' &&
          value.trim().length === 0
        ) {
          warn(
            `The value for a v-bind expression cannot be empty. Found in "v-bind:${name}"`
          )
        }
        if (modifiers) {
          if (modifiers.prop && !isDynamic) {
            name = camelize(name)
            if (name === 'innerHtml') name = 'innerHTML'
          }
          if (modifiers.camel && !isDynamic) {
            name = camelize(name)
          }
          if (modifiers.sync) {
            syncGen = genAssignmentCode(value, `$event`)
            if (!isDynamic) {
              addHandler(
                el,
                `update:${camelize(name)}`,
                syncGen,
                null,
                false,
                warn,
                list[i]
              )
              if (hyphenate(name) !== camelize(name)) {
                addHandler(
                  el,
                  `update:${hyphenate(name)}`,
                  syncGen,
                  null,
                  false,
                  warn,
                  list[i]
                )
              }
            } else {
              // handler w/ dynamic event name
              addHandler(
                el,
                `"update:"+(${name})`,
                syncGen,
                null,
                false,
                warn,
                list[i],
                true // dynamic
              )
            }
          }
        }
        if ((modifiers && modifiers.prop) || (
          !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
        )) {
          addProp(el, name, value, list[i], isDynamic)
        } else {
          //把其他的属性添加到el种的attrs数组里面，然后将el.plain设置为false
          addAttr(el, name, value, list[i], isDynamic)
        }
      } else if (onRE.test(name)) { // v-on或者@
        //把v-on或者@去掉
        name = name.replace(onRE, '')
        isDynamic = dynamicArgRE.test(name)
        if (isDynamic) {
          name = name.slice(1, -1)
        }
        //解析事件的修饰符，然后修改一下name的值类型,然后将AST对象的plain设置为false
        addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
      } else { // normal directives
        name = name.replace(dirRE, '')
        // parse arg
        const argMatch = name.match(argRE)
        let arg = argMatch && argMatch[1]
        isDynamic = false
        if (arg) {
          name = name.slice(0, -(arg.length + 1))
          if (dynamicArgRE.test(arg)) {
            arg = arg.slice(1, -1)
            isDynamic = true
          }
        }
        addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
        if (process.env.NODE_ENV !== 'production' && name === 'model') {
          checkForAliasModel(el, value)
        }
      }
    } else {
      //当前解析的内容为静态属性或者是自定义传值的静态属性
      if (process.env.NODE_ENV !== 'production') {
        const res = parseText(value, delimiters)
        if (res) {
          warn(
            `${name}="${value}": ` +
            'Interpolation inside attributes has been removed. ' +
            'Use v-bind or the colon shorthand instead. For example, ' +
            'instead of <div id="{{ val }}">, use <div :id="val">.',
            list[i]
          )
        }
      }
      addAttr(el, name, JSON.stringify(value), list[i])
      // #6887 firefox doesn't update muted state if set via attribute
      // even immediately after element creation
      if (!el.component &&
        name === 'muted' &&
        platformMustUseProp(el.tag, el.attrsMap.type, name)) {
        addProp(el, name, 'true', list[i])
      }
    }
  }
}

/**
 * 检查ref属性是否在v-for指令所在元素的子类里面
 * @param {*} el 
 */
function checkInFor(el: ASTElement): boolean {
  let parent = el
  while (parent) {
    if (parent.for !== undefined) {
      return true
    }
    parent = parent.parent
  }
  return false
}

function parseModifiers(name: string): Object | void {
  const match = name.match(modifierRE)
  if (match) {
    const ret = {}
    match.forEach(m => { ret[m.slice(1)] = true })
    return ret
  }
}


/**
 * 就是将解析到的属性改为key-value形式
 * 输入：attrs =  {  end: 13, name: "id", start: 5, value: "app" }
 * 输出: map = { id: 'app' }
 */
function makeAttrsMap(attrs: Array<Object>): Object {
  const map = {}
  for (let i = 0, l = attrs.length; i < l; i++) {
    if (
      process.env.NODE_ENV !== 'production' &&
      map[attrs[i].name] && !isIE && !isEdge
    ) {
      warn('duplicate attribute: ' + attrs[i].name, attrs[i])
    }
    map[attrs[i].name] = attrs[i].value
  }
  return map
}

// for script (e.g. type="x/template") or style, do not decode content
function isTextTag(el): boolean {
  return el.tag === 'script' || el.tag === 'style'
}

function isForbiddenTag(el): boolean {
  return (
    el.tag === 'style' ||
    (el.tag === 'script' && (
      !el.attrsMap.type ||
      el.attrsMap.type === 'text/javascript'
    ))
  )
}

const ieNSBug = /^xmlns:NS\d+/
const ieNSPrefix = /^NS\d+:/
/**
 * 解析svg标签在IE浏览器下的兼容问题
 * 输入：<svg xmlns:feature="http://www.openplans.org/topp"></svg>
 * 输出：<svg xmlns:NS1="" NS1:xmlns:feature="http://www.openplans.org/topp"></svg>
 * 输入：
       attrs = [
        {
          name: 'xmlns:NS1',
          value: ''
        },
        {
          name: 'NS1:xmlns:feature',
          value: 'http://www.openplans.org/topp'
        }
      ]
输出:
      attrs = [
        {
          name: 'xmlns:feature',
          value: 'http://www.openplans.org/topp'
        }
      ]
 * @param {} attrs 
 */
/* istanbul ignore next */
function guardIESVGBug(attrs) {
  const res = []
  for (let i = 0; i < attrs.length; i++) {
    const attr = attrs[i]
    if (!ieNSBug.test(attr.name)) {
      attr.name = attr.name.replace(ieNSPrefix, '')
      res.push(attr)
    }
  }
  return res
}

function checkForAliasModel(el, value) {
  let _el = el
  while (_el) {
    if (_el.for && _el.alias === value) {
      warn(
        `<${el.tag} v-model="${value}">: ` +
        `You are binding v-model directly to a v-for iteration alias. ` +
        `This will not be able to modify the v-for source array because ` +
        `writing to the alias is like modifying a function local variable. ` +
        `Consider using an array of objects and use v-model on an object property instead.`,
        el.rawAttrsMap['v-model']
      )
    }
    _el = _el.parent
  }
}
